🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / data / src / commonMain / kotlin / org / meshtastic / core / data / manager / MeshConfigFlowManagerImpl.kt
Displaying Raw • Download
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt 3ca87fa0323214c7204c9d37acfe9d4b6ab6ea8b (3ca87fa0) Text, 16.90 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.atomicfu.atomic
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787org.koin.core.annotation.Named
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.common.util.handledLaunch
Tff7b72import T7ee787org.meshtastic.core.common.util.safeCatching
Tff7b72import T7ee787org.meshtastic.core.model.ConnectionState
Tff7b72import T7ee787org.meshtastic.core.model.DeviceVersion
Tff7b72import T7ee787org.meshtastic.core.repository.CommandSender
Tff7b72import T7ee787org.meshtastic.core.repository.HandshakeConstants
Tff7b72import T7ee787org.meshtastic.core.repository.MeshConfigFlowManager
Tff7b72import T7ee787org.meshtastic.core.repository.MeshConnectionManager
Tff7b72import T7ee787org.meshtastic.core.repository.NodeManager
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.NotificationPrefs
Tff7b72import T7ee787org.meshtastic.core.repository.PlatformAnalytics
Tff7b72import T7ee787org.meshtastic.core.repository.RadioConfigRepository
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceStateWriter
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetadata
Tff7b72import T7ee787org.meshtastic.proto.FileInfo
Tff7b72import T7ee787org.meshtastic.proto.FirmwareEdition
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.NodeInfo
Tff7b72import T7ee787org.meshtastic.core.model.MyNodeInfo Tff7b72as Te6edf3SharedMyNodeInfo
Tff7b72import T7ee787org.meshtastic.proto.MyNodeInfo Tff7b72as Te6edf3ProtoMyNodeInfo
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tf0883e@Single
Tff7b72class T56d364MeshConfigFlowManagerImplTb4b4b4(
Tff7b72private Tff7b72val Te6edf3nodeManagerTb4b4b4: Te6edf3NodeManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3connectionManagerTb4b4b4: Te6edf3LazyTff7b72<Te6edf3MeshConnectionManagerTff7b72>Tb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3radioConfigRepositoryTb4b4b4: Te6edf3RadioConfigRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3serviceStateWriterTb4b4b4: Te6edf3ServiceStateWriterTb4b4b4,
Tff7b72private Tff7b72val Te6edf3analyticsTb4b4b4: Te6edf3PlatformAnalyticsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3commandSenderTb4b4b4: Te6edf3CommandSenderTb4b4b4,
Tff7b72private Tff7b72val Te6edf3heartbeatSenderTb4b4b4: Te6edf3DataLayerHeartbeatSenderTb4b4b4,
Tff7b72private Tff7b72val Te6edf3notificationPrefsTb4b4b4: Te6edf3NotificationPrefsTb4b4b4,
Tf0883e@NamedTb4b4b4(Ta5d6ff"Ta5d6ffServiceScopeTa5d6ff"Tb4b4b4) Tff7b72private Tff7b72val Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3MeshConfigFlowManager Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3wantConfigDelay Tff7b72= T79c0ff1T79c0ff0T79c0ff0L
T8b949e/** Monotonically increasing generation so async clears from a stale handshake are discarded. */
Tff7b72private Tff7b72val Te6edf3handshakeGeneration Tff7b72= Te6edf3atomicTb4b4b4(T79c0ff0LTb4b4b4)
T8b949e/**
* Type-safe handshake state machine. Each state carries exactly the data that is valid during that phase,
* eliminating the possibility of accessing stale or uninitialized fields.
*
* Guards [handleConfigComplete] so that duplicate or out-of-order `config_complete_id` signals from the firmware
* cannot trigger the wrong stage handler or drive the state machine backward.
*/
Tff7b72private Tff7b72sealed Tff7b72class T56d364HandshakeState Tb4b4b4{
T8b949e/** No handshake in progress. */
Tff7b72data Tff7b72object T56d364Idle Tb4b4b4: Te6edf3HandshakeStateTb4b4b4(Tb4b4b4)
T8b949e/**
* Stage 1: receiving device config, module config, channels, and metadata.
*
* [rawMyNodeInfo] arrives first (my_info packet); [metadata] may arrive shortly after. Both are consumed
* together by [buildMyNodeInfo] at Stage 1 completion. Some firmware/network paths can deliver NodeInfo before
* the Stage 2 request; keep those packets so the later node-list phase can still make progress.
*/
Tff7b72data Tff7b72class T56d364ReceivingConfigTb4b4b4(
Tff7b72val Te6edf3rawMyNodeInfoTb4b4b4: Te6edf3ProtoMyNodeInfoTb4b4b4,
Tff7b72val Te6edf3metadataTb4b4b4: Te6edf3DeviceMetadata? Tff7b72= Tff7b72nullTb4b4b4,
Tff7b72val Te6edf3earlyNodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeInfoTff7b72> Tff7b72= Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3HandshakeStateTb4b4b4(Tb4b4b4)
T8b949e/**
* Stage 2: receiving node-info packets from the firmware.
*
* [myNodeInfo] was committed at the Stage 1→2 transition. [nodes] accumulates [NodeInfo] packets until
* `config_complete_id` arrives.
*/
Tff7b72data Tff7b72class T56d364ReceivingNodeInfoTb4b4b4(Tff7b72val Te6edf3myNodeInfoTb4b4b4: Te6edf3SharedMyNodeInfoTb4b4b4, Tff7b72val Te6edf3nodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeInfoTff7b72> Tff7b72= Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4:
Te6edf3HandshakeStateTb4b4b4(Tb4b4b4)
T8b949e/** Both stages finished. The app is fully connected. */
Tff7b72data Tff7b72class T56d364CompleteTb4b4b4(Tff7b72val Te6edf3myNodeInfoTb4b4b4: Te6edf3SharedMyNodeInfoTb4b4b4) Tb4b4b4: Te6edf3HandshakeStateTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3handshakeState Tff7b72= Te6edf3atomicTff7b72<Te6edf3HandshakeStateTff7b72>Tb4b4b4(Te6edf3HandshakeStateTb4b4b4.Te6edf3IdleTb4b4b4)
Tff7b72override Tff7b72val Te6edf3newNodeCountTb4b4b4: Tffa657Int
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72=
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3state Tff7b72= Te6edf3handshakeStateTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingConfig Tff7b72-Tff7b72> Te6edf3stateTb4b4b4.Te6edf3earlyNodesTb4b4b4.Te6edf3size
Tff7b72is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingNodeInfo Tff7b72-Tff7b72> Te6edf3stateTb4b4b4.Te6edf3nodesTb4b4b4.Te6edf3size
Tff7b72else Tff7b72-Tff7b72> T79c0ff0
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleConfigCompleteTb4b4b4(Te6edf3configCompleteIdTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3state Tff7b72= Te6edf3handshakeStateTb4b4b4.Te6edf3value
Tff7b72when Tb4b4b4(Te6edf3configCompleteIdTb4b4b4) Tb4b4b4{
Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCE Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3state Tff7b72!is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingConfigTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring Stage 1 config_complete in state=Tffd700$Te6edf3stateTa5d6ff" Tb4b4b4}
Tff7b72return
Tb4b4b4}
Te6edf3handleConfigOnlyCompleteTb4b4b4(Te6edf3stateTb4b4b4)
Tb4b4b4}
Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCE Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3state Tff7b72!is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingNodeInfoTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring Stage 2 config_complete in state=Tffd700$Te6edf3stateTa5d6ff" Tb4b4b4}
Tff7b72return
Tb4b4b4}
Te6edf3handleNodeInfoCompleteTb4b4b4(Te6edf3stateTb4b4b4)
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffConfig complete id mismatch: Tffd700$Te6edf3configCompleteIdTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhandleConfigOnlyCompleteTb4b4b4(Te6edf3stateTb4b4b4: Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingConfigTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffConfig-only complete (Stage 1)Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3finalizedInfo Tff7b72= Te6edf3buildMyNodeInfoTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3rawMyNodeInfoTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3metadataTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3finalizedInfo Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffStage 1 failed: could not build MyNodeInfo, retrying Stage 1Ta5d6ff" Tb4b4b4}
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3HandshakeStateTb4b4b4.Te6edf3Idle
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3wantConfigDelayTb4b4b4)
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3startConfigOnlyTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72return
Tb4b4b4}
T8b949e// Warn if firmware is below the absolute minimum supported version.
T8b949e// The UI layer already enforces this via FirmwareVersionCheck, so we just log here
T8b949e// for diagnostics rather than hard-disconnecting.
Te6edf3finalizedInfoTb4b4b4.Te6edf3firmwareVersionTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3fwVersion Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3DeviceVersionTb4b4b4(Te6edf3fwVersionTb4b4b4) Tff7b72< Te6edf3DeviceVersionTb4b4b4(Te6edf3DeviceVersionTb4b4b4.Te6edf3ABS_MIN_FW_VERSIONTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{
Ta5d6ff"Ta5d6ffFirmware Tffd700$Te6edf3fwVersionTa5d6ff is below minimum Tffd700${Te6edf3DeviceVersionTb4b4b4.Te6edf3ABS_MIN_FW_VERSIONTffd700}Ta5d6ff — Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffprotocol incompatibilities may occurTa5d6ff"
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingNodeInfoTb4b4b4(Te6edf3myNodeInfo Tff7b72= Te6edf3finalizedInfoTb4b4b4, Te6edf3nodes Tff7b72= Te6edf3stateTb4b4b4.Te6edf3earlyNodesTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffmyNodeInfo committedTa5d6ff" Tb4b4b4}
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onRadioConfigLoadedTb4b4b4(Tb4b4b4)
Te6edf3serviceStateWriterTb4b4b4.Te6edf3setConnectionProgressTb4b4b4(Ta5d6ff"Ta5d6ffLoading node listTa5d6ff"Tb4b4b4)
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Te6edf3delayTb4b4b4(Te6edf3wantConfigDelayTb4b4b4)
Te6edf3heartbeatSenderTb4b4b4.Te6edf3sendHeartbeatTb4b4b4(Ta5d6ff"Ta5d6ffinter-stageTa5d6ff"Tb4b4b4)
Te6edf3delayTb4b4b4(Te6edf3wantConfigDelayTb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffRequesting NodeInfo (Stage 2)Ta5d6ff" Tb4b4b4}
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3startNodeInfoOnlyTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeProgressTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffhandleNodeInfoCompleteTb4b4b4(Te6edf3stateTb4b4b4: Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingNodeInfoTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffNodeInfo complete (Stage 2)Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3info Tff7b72= Te6edf3stateTb4b4b4.Te6edf3myNodeInfo
T8b949e// Transition state immediately (synchronously) to prevent duplicate handling.
T8b949e// The async work below (DB writes, broadcasts) proceeds without the guard.
T8b949e// Because nodes is now immutable, no snapshot is needed — state.nodes IS the snapshot.
T8b949e// Any stall-guard retry that re-enters handleNodeInfo will see Complete state and be ignored.
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3HandshakeStateTb4b4b4.Te6edf3CompleteTb4b4b4(Te6edf3myNodeInfo Tff7b72= Te6edf3infoTb4b4b4)
T8b949e// Cancel the transport-aware fast-recovery watchdog SYNCHRONOUSLY, before the async DB
T8b949e// install work below is launched. The firmware handshake has already completed at this
T8b949e// point (NODE_INFO_NONCE received); a slow Room commit on a large mesh would otherwise
T8b949e// falsely trip the 12s fast-recovery timeout before onNodeDbReady() gets a chance to
T8b949e// cancel it. onNodeDbReady() still performs the same cancel as part of its larger post-
T8b949e// NodeDB side-effect set, but it runs only after the DB install block finishes.
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeCompleteTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3entities Tff7b72=
Te6edf3stateTb4b4b4.Te6edf3nodesTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Te6edf3nodeInfo Tff7b72-Tff7b72>
Te6edf3nodeManagerTb4b4b4.Te6edf3installNodeInfoTb4b4b4(Te6edf3nodeInfoTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNumTff7b72[Te6edf3nodeInfoTb4b4b4.Te6edf3numTff7b72]
Tff7b72?: Te6edf3run Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffNode Tffd700${Te6edf3nodeInfoTb4b4b4.Te6edf3numTffd700}Ta5d6ff missing from DB after installNodeInfo; skippingTa5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tb4b4b4}
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3nodeRepositoryTb4b4b4.Te6edf3installConfigTb4b4b4(Te6edf3infoTb4b4b4, Te6edf3entitiesTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffPost-handshake NodeDB install failed; restarting transport to recoverTa5d6ff" Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72falseTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72falseTb4b4b4)
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3recoverPostHandshakeFailureTb4b4b4(Tb4b4b4)
Tff7b72returnTf0883e@handledLaunch
Tb4b4b4}
Te6edf3nodeManagerTb4b4b4.Te6edf3setNodeDbReadyTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setAllowNodeDbWritesTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3serviceStateWriterTb4b4b4.Te6edf3setConnectionStateTb4b4b4(Te6edf3ConnectionStateTb4b4b4.Te6edf3ConnectedTb4b4b4)
Te6edf3safeCatching Tb4b4b4{ Te6edf3analyticsTb4b4b4.Te6edf3setDeviceAttributesTb4b4b4(Te6edf3infoTb4b4b4.Te6edf3firmwareVersion Tff7b72?: Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4, Te6edf3infoTb4b4b4.Te6edf3model Tff7b72?: Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to set post-handshake analytics attributesTa5d6ff" Tb4b4b4} Tb4b4b4}
Te6edf3safeCatching Tb4b4b4{ Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onNodeDbReadyTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffPost-connected onNodeDbReady side effects failedTa5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}
T8b949e// Note: onHandshakeProgress() is intentionally NOT called here. By this point the
T8b949e// handshake has reached HandshakeState.Complete and the synchronous onHandshakeComplete()
T8b949e// call above has already cancelled the watchdog. Re-arming via onHandshakeProgress()
T8b949e// would be both semantically wrong and wasted work. The remaining onHandshakeProgress
T8b949e// sites cover all genuine progress.
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleMyInfoTb4b4b4(Te6edf3myInfoTb4b4b4: Te6edf3ProtoMyNodeInfoTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffMyNodeInfo receivedTa5d6ff" Tb4b4b4}
T8b949e// Transition to Stage 1, discarding any stale data from a prior interrupted handshake.
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingConfigTb4b4b4(Te6edf3rawMyNodeInfo Tff7b72= Te6edf3myInfoTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setMyNodeNumTb4b4b4(Te6edf3myInfoTb4b4b4.Te6edf3my_node_numTb4b4b4)
Te6edf3nodeManagerTb4b4b4.Te6edf3setFirmwareEditionTb4b4b4(Te6edf3myInfoTb4b4b4.Te6edf3firmware_editionTb4b4b4)
Te6edf3applyEventFirmwareNotificationDefaultsTb4b4b4(Te6edf3myInfoTb4b4b4.Te6edf3firmware_editionTb4b4b4)
T8b949e// Bump the generation so that a pending clear from a prior (interrupted) handshake
T8b949e// will see a stale snapshot and skip its writes, preventing it from wiping config
T8b949e// that was saved by this (newer) handshake's incoming packets.
Tff7b72val Te6edf3gen Tff7b72= Te6edf3handshakeGenerationTb4b4b4.Te6edf3incrementAndGetTb4b4b4(Tb4b4b4)
T8b949e// Clear persisted radio config so the new handshake starts from a clean slate.
T8b949e// DataStore serializes its own writes, so the clear will precede subsequent
T8b949e// setLocalConfig / updateChannelSettings calls dispatched by later packets in this
T8b949e// session (handleFromRadio processes packets sequentially, so later dispatches always
T8b949e// occur after this one returns).
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3handshakeGenerationTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3genTb4b4b4) Tff7b72returnTf0883e@handledLaunch T8b949e// Stale handshake; skip.
Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3clearChannelSetTb4b4b4(Tb4b4b4)
Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3clearLocalConfigTb4b4b4(Tb4b4b4)
Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3clearLocalModuleConfigTb4b4b4(Tb4b4b4)
Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3clearDeviceUIConfigTb4b4b4(Tb4b4b4)
Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3clearFileManifestTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeProgressTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleLocalMetadataTb4b4b4(Te6edf3metadataTb4b4b4: Te6edf3DeviceMetadataTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffLocal Metadata received: Tffd700${Te6edf3metadataTb4b4b4.Te6edf3firmware_versionTffd700}Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3state Tff7b72= Te6edf3handshakeStateTb4b4b4.Te6edf3value
Tff7b72if Tb4b4b4(Te6edf3state Tff7b72is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingConfigTb4b4b4) Tb4b4b4{
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3stateTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3metadata Tff7b72= Te6edf3metadataTb4b4b4)
T8b949e// Persist the metadata immediately — buildMyNodeInfo() reads it at Stage 1 complete,
T8b949e// but the DB write does not need to wait until then.
Tff7b72if Tb4b4b4(Te6edf3metadata Tff7b72!Tff7b72= Te6edf3DeviceMetadataTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3insertMetadataTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3rawMyNodeInfoTb4b4b4.Te6edf3my_node_numTb4b4b4, Te6edf3metadataTb4b4b4) Tb4b4b4}
Tb4b4b4}
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeProgressTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring metadata outside Stage 1 (state=Tffd700$Te6edf3stateTa5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleNodeInfoTb4b4b4(Te6edf3infoTb4b4b4: Te6edf3NodeInfoTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3state Tff7b72= Te6edf3handshakeStateTb4b4b4.Te6edf3value
Tff7b72when Tb4b4b4(Te6edf3stateTb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingConfig Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffBuffering NodeInfo received during Stage 1Ta5d6ff" Tb4b4b4}
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3stateTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3earlyNodes Tff7b72= Te6edf3stateTb4b4b4.Te6edf3earlyNodesTb4b4b4.Te6edf3withNodeInfoTb4b4b4(Te6edf3infoTb4b4b4)Tb4b4b4)
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeProgressTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72is Te6edf3HandshakeStateTb4b4b4.Te6edf3ReceivingNodeInfo Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3handshakeStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3stateTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3nodes Tff7b72= Te6edf3stateTb4b4b4.Te6edf3nodesTb4b4b4.Te6edf3withNodeInfoTb4b4b4(Te6edf3infoTb4b4b4)Tb4b4b4)
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeProgressTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffIgnoring NodeInfo outside active handshake (state=Tffd700$Te6edf3stateTa5d6ff)Ta5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffhandleFileInfoTb4b4b4(Te6edf3infoTb4b4b4: Te6edf3FileInfoTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffFileInfo received: Tffd700${Te6edf3infoTb4b4b4.Te6edf3file_nameTffd700}Ta5d6ff (Tffd700${Te6edf3infoTb4b4b4.Te6edf3size_bytesTffd700}Ta5d6ff bytes)Ta5d6ff" Tb4b4b4}
Te6edf3scopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{ Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3addFileInfoTb4b4b4(Te6edf3infoTb4b4b4) Tb4b4b4}
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3onHandshakeProgressTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8fftriggerWantConfigTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3connectionManagerTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3startConfigOnlyTb4b4b4(Tb4b4b4)
Tb4b4b4}
T8b949e/**
* Builds a [SharedMyNodeInfo] from the raw proto and optional firmware metadata. Pure function — no side effects.
* Returns null only if construction throws.
*/
Tff7b72private Tff7b72fun Td2a8ffbuildMyNodeInfoTb4b4b4(Te6edf3rawTb4b4b4: Te6edf3ProtoMyNodeInfoTb4b4b4, Te6edf3metadataTb4b4b4: Te6edf3DeviceMetadata?Tb4b4b4)Tb4b4b4: Te6edf3SharedMyNodeInfo? Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3withTb4b4b4(Te6edf3rawTb4b4b4) Tb4b4b4{
Te6edf3SharedMyNodeInfoTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3my_node_numTb4b4b4,
Te6edf3hasGPS Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3model Tff7b72=
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3hwModel Tff7b72= Te6edf3metadataTff7b72?.Te6edf3hw_modelTb4b4b4) Tb4b4b4{
Tff7b72nullTb4b4b4,
Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4,
Tff7b72-Tff7b72> Tff7b72null
Tff7b72else Tff7b72-Tff7b72> Te6edf3hwModelTb4b4b4.Te6edf3nameTb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff'_'Tb4b4b4, Ta5d6ff'-'Tb4b4b4)Tb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff'p'Tb4b4b4, Ta5d6ff'.'Tb4b4b4)Tb4b4b4.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tb4b4b4}Tb4b4b4,
Te6edf3firmwareVersion Tff7b72= Te6edf3metadataTff7b72?.Te6edf3firmware_versionTff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3couldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3shouldUpdate Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3currentPacketId Tff7b72= Te6edf3commandSenderTb4b4b4.Te6edf3getCurrentPacketIdTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xffffffffLTb4b4b4,
Te6edf3messageTimeoutMsec Tff7b72= T79c0ff3T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3minAppVersion Tff7b72= Te6edf3min_app_versionTb4b4b4,
Te6edf3maxChannels Tff7b72= T79c0ff8Tb4b4b4,
Te6edf3hasWifi Tff7b72= Te6edf3metadataTff7b72?.Te6edf3hasWifi Tff7b72=Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3channelUtilization Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3airUtilTx Tff7b72= T79c0ff0fTb4b4b4,
Te6edf3deviceId Tff7b72= Te6edf3device_idTb4b4b4.Te6edf3utf8Tb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3pioEnv Tff7b72= Te6edf3pio_envTb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Tff7b72null Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4) Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3exTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to build MyNodeInfoTa5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffapplyEventFirmwareNotificationDefaultsTb4b4b4(Te6edf3editionTb4b4b4: Te6edf3FirmwareEditionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3edition Tff7b72!Tff7b72= Te6edf3FirmwareEditionTb4b4b4.Te6edf3VANILLATb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3notificationPrefsTb4b4b4.Te6edf3nodeEventsAutoDisabledForEventTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4{
Te6edf3notificationPrefsTb4b4b4.Te6edf3setNodeEventsEnabledTb4b4b4(Tff7b72falseTb4b4b4)
Te6edf3notificationPrefsTb4b4b4.Te6edf3setNodeEventsAutoDisabledForEventTb4b4b4(Tff7b72trueTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3notificationPrefsTb4b4b4.Te6edf3nodeEventsAutoDisabledForEventTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4{
Te6edf3notificationPrefsTb4b4b4.Te6edf3setNodeEventsEnabledTb4b4b4(Tff7b72trueTb4b4b4)
Te6edf3notificationPrefsTb4b4b4.Te6edf3setNodeEventsAutoDisabledForEventTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffListTff7b72<Te6edf3NodeInfoTff7b72>Tb4b4b4.Te6edf3withNodeInfoTb4b4b4(Te6edf3infoTb4b4b4: Te6edf3NodeInfoTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeInfoTff7b72> Tb4b4b4{
Tff7b72val Te6edf3index Tff7b72= Te6edf3indexOfFirst Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tff7b72=Tff7b72= Te6edf3infoTb4b4b4.Te6edf3num Tb4b4b4}
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3index Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3toMutableListTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3apply Tb4b4b4{ Tff7b72thisTff7b72[Te6edf3indexTff7b72] Tff7b72= Te6edf3info Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72this Tff7b72+ Te6edf3info
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.4.2 - Generated in 0.12s